package db

import (
	"core/env"
	"core/graylog"
	"core/rds"
	"core/utils"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

type Invitee struct {
	UID          int64
	GotReward    bool
	InviteAtUnix int64
}

type User struct {
	UID        int64
	OpenID     string
	SessionKey string
	UnionId    string
	Name       string
	HeadImg    string
	Gender     int
	Age        int
	Province   string
	City       string

	GotFreeGrassAt  time.Time
	Level           map[int]map[int]LevelDetail
	Grass           int
	Water           int
	FinishLevelAt   time.Time
	GotCardPkgAt    time.Time
	Authorize       bool
	CreateAt        time.Time
	UpdateAt        time.Time
	DailyAt         time.Time
	WatchVideoCount int
	Loves           map[string]int
	Invitor         int64
	Invitees        []Invitee
}

const (
	lockExpire   = time.Second * 3    // 上锁过期时间
	userExpire   = time.Hour * 24 * 3 // 用户过期时间
	CustomMenuID = 1000000            // 拼图圈 menuId

	FieldOpenID     = "openId"
	FieldSessionKey = "sessionKey"
	FieldUnionId    = "unionId"
	FieldName       = "name"
	FieldHeadImg    = "headImg"
	FieldGender     = "gender"
	FieldAge        = "age"
	FieldProvince   = "province"
	FieldCity       = "city"

	FieldGotFreeGrassAt  = "gotFreeGrassAt"
	FieldGrass           = "grass"
	FieldWater           = "water"
	FieldFinishLevelAt   = "finishLevelAt"
	FieldGotCardPkgAt    = "gotCardPkgAt"
	FieldAuthorize       = "authorize"
	FieldCreateAt        = "createAt"
	FieldUpdateAt        = "updateAt"
	FieldWatchVideoCount = "watchVideoCount"
	FieldDailyAt         = "dailyAt"
	FieldLoves           = "loves"
	FieldInvitor         = "invitor"
	FieldInvitee         = "invitee"
)

var (
	ErrNotFound = errors.New("未找到，请刷新后重试")
	FieldsAll   = []string{
		FieldOpenID, FieldSessionKey, FieldUnionId, FieldName, FieldHeadImg, FieldGender, FieldAge, FieldProvince, FieldCity,
		FieldGotFreeGrassAt, FieldGrass, FieldWater, FieldFinishLevelAt, FieldGotCardPkgAt, FieldAuthorize, FieldCreateAt, FieldUpdateAt, FieldWatchVideoCount, FieldDailyAt,
		FieldLoves, FieldInvitor, FieldInvitee,
	}
	FieldsBasic = []string{
		FieldName, FieldHeadImg, FieldGender, FieldAge, FieldProvince, FieldCity,
	}
)

func MakeUserKey(uid int64) string {
	return fmt.Sprintf("user:%d", uid)
}
func makeLockKey(uid int64) string {
	return fmt.Sprintf("user:%d:lock", uid)
}
func makeCounterKey() string {
	return "user:counter"
}
func loadBasicData(uid int64) (u *User, err error) {
	data, err := rds.GetCacheClient(uid).HGetAll(MakeUserKey(uid)).Result()
	if err != nil {
		return
	}
	if data["openId"] == "" {
		u, err = LoadFromMysql(uid)
		if err != nil {
			return
		}
		if u.Invitees == nil {
			u.Invitees = make([]Invitee, 0)
		}
		if u.Loves == nil {
			u.Loves = make(map[string]int)
		}
		err = SaveToRedis(u)
		if err != nil {
			return
		}
		go graylog.Info("缓存击穿", graylog.M{
			"playerId": u.UID,
			"data":     fmt.Sprintf("%+v", u),
		})
		return
	}
	u = &User{
		UID:      uid,
		Invitees: make([]Invitee, 0),
		Loves:    make(map[string]int),
	}

	for k, v := range data {
		switch {
		case k == FieldOpenID:
			u.OpenID = v
		case k == FieldSessionKey:
			u.SessionKey = v
		case k == FieldUnionId:
			u.UnionId = v
		case k == FieldName:
			u.Name = v
		case k == FieldHeadImg:
			u.HeadImg = v
		case k == FieldGender:
			u.Gender = utils.ToInt(v)
		case k == FieldAge:
			u.Age = utils.ToInt(v)
		case k == FieldProvince:
			u.Province = v
		case k == FieldCity:
			u.City = v

		case k == FieldGotFreeGrassAt:
			u.GotFreeGrassAt = utils.ToTime(v)
		case k == FieldGrass:
			u.Grass = utils.ToInt(v)
		case k == FieldWater:
			u.Water = utils.ToInt(v)
		case k == FieldFinishLevelAt:
			u.FinishLevelAt = utils.ToTime(v)
		case k == FieldGotCardPkgAt:
			u.GotCardPkgAt = utils.ToTime(v)
		case k == FieldAuthorize:
			u.Authorize = utils.ToBool(v)
		case k == FieldCreateAt:
			u.CreateAt = utils.ToTime(v)
		case k == FieldUpdateAt:
			u.UpdateAt = utils.ToTime(v)
		case k == FieldDailyAt:
			u.DailyAt = utils.ToTime(v)
		case k == FieldWatchVideoCount:
			u.WatchVideoCount = utils.ToInt(v)
		case k == FieldLoves:
			err = json.Unmarshal([]byte(v), &u.Loves)
			if err != nil {
				return
			}
			if u.Loves == nil {
				u.Loves = make(map[string]int)
			}
		case k == FieldInvitor:
			u.Invitor = utils.ToInt64(v)
		case strings.HasPrefix(k, FieldInvitee):
			uid := utils.ToInt64(strings.TrimPrefix(k, FieldInvitee))
			u.Invitees = append(u.Invitees, *(decodeInvitee(uid, v)))
		}
	}
	sort.Sort(SortInvitees(u.Invitees))
	return
}
func (u *User) checkDailyAt() (err error) {
	now := time.Now()
	todayBeginAt := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
	if u.DailyAt.After(todayBeginAt) {
		return
	}
	u.WatchVideoCount = 0
	u.DailyAt = now
	err = u.Save(FieldWatchVideoCount, FieldDailyAt)
	return
}

func Lock(uid int64) bool {
	return rds.GetCacheClient(uid).SetNX(makeLockKey(uid), "", lockExpire).Val()
}
func Unlock(uid int64) {
	rds.GetCacheClient(uid).Del(makeLockKey(uid))
	return
}

// 如果没有数据，则去mysql取（检测每日重置）
func LoadByUID(uid int64, updateDaily bool) (u *User, err error) {
	u, err = loadBasicData(uid)
	if err != nil {
		return
	}
	err = loadLevelData(u)
	if err != nil {
		return
	}
	if updateDaily {
		err = u.checkDailyAt()
		if err != nil {
			return
		}
	}
	return
}

func LoadByIds(ids []int64) (users map[int64]*User) {
	users = make(map[int64]*User)
	for _, uid := range ids {
		u, err := loadBasicData(uid)
		if err != nil {
			utils.Error("查找uid失败", uid)
			continue
		}
		users[uid] = u
	}
	return
}

func Create(openId, sessionKey, unionId string, invitorUID int64, preUser *User) (u *User, err error) {
	u = &User{
		OpenID:     openId,
		SessionKey: sessionKey,
		UnionId:    unionId,

		Grass:          10,
		GotFreeGrassAt: time.Now(),
		CreateAt:       time.Now(),
		Invitor:        invitorUID,
		Level: map[int]map[int]LevelDetail{
			1: {
				env.GetJigsawConfByIndexId(1, 1): LevelDetail{
					Unlock:    true,
					FinishNum: 0,
				},
			},
		},
	}
	// 走预设值
	if preUser.Grass > 0 {
		u.Grass = preUser.Grass
	}

	defaultClient := rds.GetDefaultClient()
	u.UID = defaultClient.Incr(makeCounterKey()).Val()
	err = CreateOpenIdData(u.OpenID, u.UID)
	if err != nil {
		utils.Error(err)
		return
	}
	defer func() {
		if err != nil {
			DelOpenIdData(u.OpenID)
		}
	}()

	err = u.Save(FieldsAll...)
	if err != nil {
		utils.Error(err)
		return
	}

	levelSets := make(map[string]interface{})
	for menuId, v := range u.Level {
		for levelId, detail := range v {
			levelSets[fmt.Sprintf("%d:%d", menuId, levelId)] = encodeLevelDetail(&detail)
		}
	}
	userLevelKey := MakeUserLevelKey(u.UID)
	cacheClient := rds.GetCacheClient(u.UID)
	if err = cacheClient.HMSet(userLevelKey, levelSets).Err(); err != nil {
		return
	}
	if err = cacheClient.Expire(userLevelKey, userExpire).Err(); err != nil {
		return
	}

	go AddActiveUser(u.UID)
	// 邀请处理
	if invitorUID > 0 {
		go func() {
			defer utils.CatchPanicAndErr("addInvitee", &err, graylog.M{"invitor": invitorUID, "invitee": u.UID})
			graylog.Info("邀请", graylog.M{
				"invitor": invitorUID,
				"invitee": u.UID,
			})
			var invitor *User
			invitor, err = LoadByUID(invitorUID, false)
			if len(invitor.Invitees) >= 150 {
				graylog.Info("邀请超过150人了", graylog.M{
					"playerId": invitor.UID,
					"invitee":  u.UID,
				})
				return
			}
			if err != nil {
				return
			}
			err = invitor.SaveInvitee(&Invitee{
				UID:          u.UID,
				GotReward:    false,
				InviteAtUnix: time.Now().Unix(),
			})
		}()
	}
	return
}

// 保存数据，level和invitee的单独保存，不用此函数
func (u *User) Save(fields ...string) (err error) {
	now := time.Now()
	u.UpdateAt = now
	sets := map[string]interface{}{
		FieldUpdateAt: now.Unix(),
	}
	for _, field := range fields {
		switch {
		case field == FieldOpenID:
			sets[FieldOpenID] = u.OpenID
		case field == FieldSessionKey:
			sets[FieldSessionKey] = u.SessionKey
		case field == FieldUnionId:
			sets[FieldUnionId] = u.UnionId
		case field == FieldName:
			sets[FieldName] = u.Name
		case field == FieldHeadImg:
			sets[FieldHeadImg] = u.HeadImg
		case field == FieldGender:
			sets[FieldGender] = u.Gender
		case field == FieldAge:
			sets[FieldAge] = u.Age
		case field == FieldProvince:
			sets[FieldProvince] = u.Province
		case field == FieldCity:
			sets[FieldCity] = u.City

		case field == FieldGotFreeGrassAt:
			sets[FieldGotFreeGrassAt] = u.GotFreeGrassAt.Unix()
		case field == FieldGrass:
			sets[FieldGrass] = u.Grass
		case field == FieldWater:
			sets[FieldWater] = u.Water
		case field == FieldFinishLevelAt:
			sets[FieldFinishLevelAt] = u.FinishLevelAt.Unix()
		case field == FieldGotCardPkgAt:
			sets[FieldGotCardPkgAt] = u.GotCardPkgAt.Unix()
		case field == FieldAuthorize:
			sets[FieldAuthorize] = strconv.FormatBool(u.Authorize)
		case field == FieldCreateAt:
			sets[FieldCreateAt] = u.CreateAt.Unix()
		case field == FieldDailyAt:
			sets[FieldDailyAt] = u.DailyAt.Unix()
		case field == FieldWatchVideoCount:
			sets[FieldWatchVideoCount] = u.WatchVideoCount
		case field == FieldLoves:
			content, _ := json.Marshal(u.Loves)
			sets[FieldLoves] = string(content)
		case field == FieldInvitor:
			sets[FieldInvitor] = u.Invitor
		case field == FieldInvitee:
			for _, invitee := range u.Invitees {
				sets[fmt.Sprintf("%s%d", FieldInvitee, invitee.UID)] = encodeInvitee(&invitee)
			}
		}
	}
	key := MakeUserKey(u.UID)
	cacheClient := rds.GetCacheClient(u.UID)
	err = cacheClient.HMSet(key, sets).Err()
	if err != nil {
		return
	}

	err = cacheClient.Expire(key, userExpire).Err()
	err = cacheClient.Expire(MakeUserLevelKey(u.UID), userExpire).Err()
	if err != nil {
		return
	}
	go AddActiveUser(u.UID)
	return
}
